[Previous][Up][Next] |
Remark: | This chapter assumes you have some experience in using this library. |
We'll see how to implement a writer for a new resource file format. A resource writer is a descendant of TAbstractResourceWriter, and it's usually implemented in a unit named namewriter, where name is file format name.
Suppose we must write a writer for file format foo; we could start with a unit like this:
unit foowriter; {$MODE OBJFPC} {$H+} interface uses Classes, SysUtils, resource; type TFooResourceWriter = class (TAbstractResourceWriter) protected function GetExtensions : string; override; function GetDescription : string; override; procedure Write(aResources : TResources; aStream : TStream); override; public constructor Create; override; end; implementation function TFooResourceWriter.GetExtensions: string; begin end; function TFooResourceWriter.GetDescription: string; begin end; procedure TFooResourceWriter.Write(aResources: TResources; aStream: TStream); begin end; constructor TFooResourceWriter.Create; begin end; initialization TResources.RegisterWriter('.foo',TFooResourceWriter); end.
Note that in the initialization section, TFooResourceWriter is registered for extension .foo.
We must implement abstract methods of TAbstractResourceWriter. Let's start with the simpler ones, GetExtensions and GetDescription.
function TFooResourceWriter.GetExtensions: string; begin Result:='.foo'; end; function TFooResourceWriter.GetDescription: string; begin Result:='FOO resource writer'; end;
Now let's see Write. This method must write all resources in the TResources object to the stream.
Suppose that our foo format is very simple:
each resource has a 16-byte header containing:
Our Write method could be something like this:
procedure TFooResourceWriter.Write(aResources: TResources; aStream: TStream); var i : integer; begin WriteFileHeader(aStream,aResources); or:=0 to aResources.Count-1 do WriteResource(aStream,aResources[i]); end;
So we must implement WriteFileHeader, which writes the 16-byte file header, and WriteResource, which writes a single resource with its header.
Let's start from the first one:
procedure TFooResourceWriter.WriteFileHeader(aStream: TStream; aResources: TResources); var signature : array[0..3] of char; rescount : longword; padding : qword; begin signature:='FOO*'; rescount:=aResources.Count; padding:=0; aStream.WriteBuffer(signature[0],4); aStream.WriteBuffer(rescount,sizeof(rescount)); aStream.WriteBuffer(padding,sizeof(padding)); end;
This code simply writes the file header as defined in foo format. Note that if we are running on a big endian system we should swap bytes before writing, e.g. calling SwapEndian function, but for simplicity this is omitted.
Now WriteResource comes. This method could be like this:
procedure TFooResourceWriter.WriteResource(aStream: TStream; aResource: TAbstractResource); var Typeongword; aName : longword; aLangID : longword; aDataSize : longword; begin Typeesource.Type aName:=aResource.Name.ID; aLangID:=aResource.LangID; aDataSize:=aResource.RawData.Size; //write resource header aStream.WriteBuffer(TypeofType)); aStream.WriteBuffer(aName,sizeof(aName)); aStream.WriteBuffer(aLangID,sizeof(aLangID)); aStream.WriteBuffer(aDataSize,sizeof(aDataSize)); //write resource data aResource.RawData.Position:=0; aStream.CopyFrom(aResource.RawData,aResource.RawData.Size); //align data so that it ends on a 4-byte boundary Align4Bytes(aStream); end;
We write the 16-byte resource header, and then resource data. Note that if resources have been loaded from a stream and the user didn't modify resource data, we are copying data directly from the original stream.
Align4Bytes is a private method (not shown for simplicity) that writes some padding bytes to the stream if needed, since FOO file format specifies that resource data must be padded to end on a 4 byte boundary. Again, we didn't consider endianess for simplicity. And finally, note that foo format only supports IDs for types and names, so if one of them is a name (that is, a string) this code might cause exceptions to be raised.
Note: More complex file formats store resources in a tree hierarchy; since TResources internally stores resources in this way too, a writer can choose to acquire a reference to the internal tree used by the TResources object (see TAbstractResourceWriter.GetTree) and use it directly. For these file formats resources can be written faster, since there is no overhead involved in building a separate resource tree in the writer.
That's all. Now you should be able to create a real resource writer.